package com.elione.modbus;


import java.net.InetAddress;
import java.io.IOException;



import net.wimpi.modbus.net.TCPMasterConnection;
import net.wimpi.modbus.io.ModbusTCPTransaction;
import net.wimpi.modbus.Modbus;
import net.wimpi.modbus.ModbusException;

import net.wimpi.modbus.msg.ReadMultipleRegistersRequest;
import net.wimpi.modbus.msg.ReadMultipleRegistersResponse;
import net.wimpi.modbus.msg.WriteMultipleRegistersRequest;
import net.wimpi.modbus.msg.WriteMultipleRegistersResponse;


import net.wimpi.modbus.procimg.Register;
import net.wimpi.modbus.procimg.SimpleRegister;


/**
 * Class that implements the MODBUS TCP master function 
 * 
 * @author     Zhang hongyu 
 *
 * @link      
 *
 * @see        
 *
 * @version    1.0 (06.04.2005)
 * @version    1.1 (28.04.2005)
 * - update at readArryItemRawValue 
 * 
 * @version    1.2 (15.12.2005) / mah
 * - add the method  writeArrayValue. Modifing the writeArrayValueBlock method
 * @version    1.3 (28.08.2007)
 * - remove the array size check at the function readArrayItemsRawValues
 * - change the paramter check code at read2DArrayItemsRawValues
 * 
 * @version    2.0 (10.12.2007 zha)
 * - update the access method for new Jamod API (version 1.2)
 * - close the TCPMasterConnection after data request
 * 
 * @version    2.1 (22.07.2008 zha)
 * - set up the connection socket timeout vlaue
 * - force to recreate the ModbusTCPTransaction and TCPMasterConnection
 * at every io access method . 
 * 
 */

public class ModbusTcpMaster  {
    private  TCPMasterConnection con = null; 
    private  ModbusTCPTransaction trans = null;
    private  boolean hasDefaultConn = false;   // flag to indicate if has default connection to slave
    private  ModbusSlaveInfo slaveInfo;
    
    
    private  final int MAX_WORD_IN_REQUEST = 120;
    private  final int MAX_WRITE_WORD      = 100;//more than 100 ist not possible  
    private  final int MAX_RETRY_NUM       = 10;
    
    /** 
     * Default constructor 
     *
     * @since   1.0
     *
     */  
    public ModbusTcpMaster() {
      hasDefaultConn = false;  
    }

    
    /**
     * Set default modbus tcp connection  to speed up data request process. 
     * Function hasDefaultConnection will return true after this operation 
     *
     * @since   1.0
     *
     * @param   slave_info  slave information such as ip, port.. 
     *
     */      
    public void setModbusTcpConnection (ModbusSlaveInfo slave_info)
      throws  java.net.UnknownHostException {
        //create the connection
        InetAddress slave_add = InetAddress.getByName(slave_info.getSlaveIP());
        con = new TCPMasterConnection(slave_add);
                
        con.setPort(slave_info.getTcpPort());
        con.setTimeout(slave_info.getSocketTimeout()); 
        
        trans = new ModbusTCPTransaction();     
        
         // TRUE - transaction after each execute       
        trans.setReconnecting(slave_info.getReconnect());   
        
        // set retry number when error occured 
        trans.setRetries(slave_info.getRetryNum());
        
        slaveInfo = slave_info;
        
        /* new in version 2.1 */
        // ignore the default connection object 
        hasDefaultConn = true;
      }
    
    /**
     * Get default slave information object which is bounded by
     * setModbusTcpConnection function
     *
     * @since   1.0
     *
     * @return  
     */        
    public ModbusSlaveInfo getDefaultSlaveInfo() {
      return (slaveInfo);
    }
    
    /**
     * Reset default modbus tcp connection. Function hasDefaultConnection will return 
     * false after this operation 
     *
     * @since   1.0
     *
     * @param   reset  set to ture to active reset; otherwise do nothing
     *
     */        
    public void resetModbusTcpConnection( boolean reset ) {
      if (reset) {
        con = null;
        trans = null;
        hasDefaultConn = false;
      }
    }
    
    
    /**
      * Check if this object has default MODBUS Tcp to slave.
      *
      * @since   1.0
      *
      * @return   true if object has default MODBUS Tcp connection 
      *
      */   
    public boolean hasDefaultConnection() {
      return (hasDefaultConn);
    }
    
    
   /**
    * Get the raw value of a signle(none-array) variable from MODBUS response 
    *
    * @since   1.0
    *
    * @version 1.0 - use default MODBUS TCP conneciton is hasDefaultConnection() equal true 
    *                otherwise create connection from slave_info,<B> but not set default connection </B>
    * 
    * @param   slave_info  slave information such as ip, port.. 
    * @param   m_signal    contains necessary information for MODBUS request 
    *
    * @return  byte[]      <B>raw value</B> of requested signal in byte array (without modbus head bytes); <br>
    *                      <B>MSB first in return array if the requested signal has WORD type </B> <br>
    *                      for example, PLC_variable_W = 16#1234, return array is  <br>
    *                      byte[0] = 16#12;byte[1] = 16#34;  <P>
    *
    *                      <B>LSB first in return array if the requested siganl has DWORD type. </B>  <br>
    *                      for example, PLC_variable_DW = 16#12345678, return array is  <br>
    *                      byte[0] = 16#56;byte[1] = 16#78;byte[2] = 16#12;byte[3] = 16#34; <br>
    *
    * @throws  ModbusCommunicationException when error occured during communication 
    *          such as network failure, illegal reqeust <br>
    *          FunctionIllegalInputException if function is called with illegal parameter    
    */  
    
    public synchronized byte[] readSignalRawValue( ModbusSlaveInfo slave_info,  ModbusSignal m_signal ) 
      throws ModbusCommunicationException,FunctionIllegalInputException, Exception {
      
      // check input parameter    
      if ((slave_info == null ) || (m_signal == null)) 
         throw new FunctionIllegalInputException("readSingleValue has illegal input ");
        
      ReadMultipleRegistersRequest req = new ReadMultipleRegistersRequest();
      String hexMessage = new String("");
      try  {
        // 1. create modbus request
        int start_add = m_signal.getAddress();       // start address     
        int length    = 0;                           // length counte in WORD;
       
        switch (m_signal.getMtype()) {
          case ModbusSignal.MTYPE_DW:             // double WORD signal 
            length = 2;
            break;
          case ModbusSignal.MTYPE_W:              // WORD signal 
            length = 1;
            break;
         default:  
        }
       
        req.setReference(start_add);
        req.setWordCount(length);
        
        req.setTransactionID(slave_info.getTransactionID());
        req.setProtocolID(slave_info.getProtocolID());
        req.setUnitID(slave_info.getUnitID());
         
         
        //2. Open the connection
        //2.1 Check if has default connection 
        //2.2 create connection only for this operation if don't have default conneciton 
        
        if (! hasDefaultConnection() ) {
          InetAddress slave_add = InetAddress.getByName(slave_info.getSlaveIP());
          con = new TCPMasterConnection(slave_add);
          con.setPort(slave_info.getTcpPort());       
          con.setTimeout(slave_info.getSocketTimeout()); 
          
          // Create transaction 
          trans = new ModbusTCPTransaction();     
          trans.setRequest(req);               
          trans.setReconnecting(slave_info.getReconnect());       
          // set retry number when error occured 
          trans.setRetries(slave_info.getRetryNum());
        }  
                  
        if (!con.isConnected()) {
          con.connect();       // throw IOException if there is a network failure                   
        }              

        trans.setConnection(con);          
        
//        System.out.println("request is :" + req.getHexMessage());

        trans.setRequest(req); 
        // send request 
        trans.execute();

        hexMessage = (trans.getResponse()).getHexMessage();
        
        //3. receive response 
        ReadMultipleRegistersResponse res = (ReadMultipleRegistersResponse) trans.getResponse();
   
       
//        System.out.println("response is :" + res.getHexMessage());
       
        //4. return raw value
        
        // new in version 2.0 
        Register[] registers = res.getRegisters();
        
        byte[] res_message = new byte[registers.length * 2];
        /* convert register to byte array . each register is 2 byte array */
        int index = 0;
        for (int i = 0;  i <registers.length; i++ ){
            byte[] regBytes = registers[i].toBytes();         
            res_message[index++] = regBytes[0];
            res_message[index++] = regBytes[1];           
        }
       

        return(res_message);
        
      } catch (java.net.UnknownHostException e) {
        throw new ModbusCommunicationException(
            "Illegal slave ip address current slave info["
            + slave_info.toString() +"]");
            
      } catch (net.wimpi.modbus.ModbusIOException e) {

        throw new ModbusCommunicationException(
            "Modbus Tcp Connection error during communication [" 
            + slave_info.toString() +"],detail error ["
            + e.toString() +"]");
        
      } catch (net.wimpi.modbus.ModbusSlaveException e) {
    
        throw new ModbusCommunicationException(
             "Modbus illeagl response from slave, the request was [" + con.getAddress().getHostAddress() 
             + " - " + req.getHexMessage() + "]" );
      } catch (ModbusException e) {
    
        throw new ModbusCommunicationException("Unknown Modbus exeception [" + e.toString() +"]");        
        
      } catch (NullPointerException e) {
    
        throw new ModbusCommunicationException(
              "No data received from slave, the request was [" + con.getAddress().getHostAddress()  
               + " - " + req.getHexMessage() +"]");
      } catch (IOException e) {

        throw new ModbusCommunicationException(
          "network failue on setup TCPMasterConnection, please check slave information in configuration file, e.g. ip, port: current Slave info[" 
          + slave_info.toString() + "] + detail information[" 
          + e.toString() +"]" );
          
      } catch(java.lang.ClassCastException e)  {         
        throw new ClassCastException(e.toString() + 
        ":response message is " + hexMessage  );    
      } finally{          
          /* new in version 2.0 */        
          con.close();
      }
      
    
    } // end of getSingleValue
    
    
    /**
     * Get one item raw value from given array. 
     *
     * @since   1.0
     *
     * @version 1.0   use default MODBUS TCP conneciton is hasDefaultConnection() return true 
     *                otherwise create connection from slave_info,<B> but not set default connection </B>
     * 
     * @version 1.1   redefine the index start from 1
     *    
     * - check paramter index can not less then 1         
     *
     * @param   slave_info  slave information such as ip, port.. 
     * @param   m_signal    contains necessary information for MODBUS request 
     * @param   index       index in array, the first item in array is 1
     *
     * @return  byte[]      <B>raw value</B> of requested signal in byte array (without modbus head bytes); <br>
     *                      <B>MSB first in return array if the requested signal has WORD type </B> <br>
     *                      for example, PLC_variable_W = 16#1234, return array is  <br>
     *                      byte[0] = 16#12;byte[1] = 16#34;  <P>
     *    
     *                      <B>LSB first in return array if the requested siganl has DWORD type. </B> <br>
     *                      for example, PLC_variable_DW = 16#12345678, return array is <br>
     *                      byte[0] = 16#56;byte[1] = 16#78;byte[2] = 16#12;byte[3] = 16#34;    <P>
     *                      return signal value if given signal is not array variable( if getArraySize() = 1 );
     *
     * @throws  ModbusCommunicationException when error occured during communication 
     *          such as network failure, illegal reqeust or requested index out of bound  <br>
     *
     *          FunctionIllegalInputException if function is called with illegal parameter 
     */   
      
    public synchronized byte[] readArrayItemRawValue(ModbusSlaveInfo slave_info,  ModbusSignal m_signal,int index) 
      throws ModbusCommunicationException,FunctionIllegalInputException,Exception{
      // check input parameter 
      if ((slave_info == null ) || (m_signal == null)) 
         throw new FunctionIllegalInputException("readArrayItem has illegal input ");    
         
      ReadMultipleRegistersRequest req = new ReadMultipleRegistersRequest();
      String hexMessage = new String("");
      try  {
      
        // 1. create Modbus request 
        int ref        = m_signal.getAddress();
        int length     = 0;                       // length counte in WORD;         
        switch (m_signal.getMtype()) {
          case ModbusSignal.MTYPE_DW:             // double WORD signal 
            length = 2;
            break;
          case ModbusSignal.MTYPE_W:              // WORD signal 
            length = 1;
            break;
         default:  
        }
        
        
        if ((index > m_signal.getArraysize()) || (index < 0 )) {
          throw new ModbusRequestItemOutofArrayBound(
               "Request item[" + index + "] form array ["
               + m_signal.getAddress()+"] which size =" + m_signal.getArraysize()); 
        }
        
        int ref_offset = 0;                          // address off set       
        // calculate address off set value, for none array variable offset = 0      
        if ( m_signal.getArraysize() > 1 )  {
          ref_offset = (index -1) * length;
        }
        
//        System.out.println("Ref_address : " + (ref+ref_offset));      
        
        req.setReference(ref + ref_offset);
        req.setWordCount(length);
        
        req.setTransactionID(slave_info.getTransactionID());
        req.setProtocolID(slave_info.getProtocolID());     
        req.setUnitID(slave_info.getUnitID());
        
        //2. Open the connection
        
        if (! hasDefaultConnection() ) {
          InetAddress slave_add = InetAddress.getByName(slave_info.getSlaveIP());
          con = new TCPMasterConnection(slave_add);
          con.setPort(slave_info.getTcpPort());         
          con.setTimeout(slave_info.getSocketTimeout()); 
          
          // Create transaction 
          trans = new ModbusTCPTransaction();     
          trans.setRequest(req); 
          trans.setReconnecting(slave_info.getReconnect());       
          // set retry number when error occured 
          trans.setRetries(slave_info.getRetryNum());        
        }  



        if (!con.isConnected()) {
          con.connect();       // throw IOException if there is a network failure       
        }
        
        trans.setConnection(con);          
        
//        System.out.println("request is :" + req.getHexMessage());
        
        //3. Send request 
        trans.setRequest(req); 
        trans.execute();
        
        hexMessage = (trans.getResponse()).getHexMessage();
       
        //4. receive response 
        ReadMultipleRegistersResponse res = (ReadMultipleRegistersResponse) trans.getResponse();
   
       
//        System.out.println("response is :" + res.getHexMessage());
       
        //5. return raw value 
      
        // new in version 2.0 
        Register[] registers = res.getRegisters();
        
        byte[] res_message = new byte[registers.length * 2];
        /* convert register to byte array . each register is 2 byte array */
        index = 0;
        for (int i = 0;  i <registers.length; i++ ){
            byte[] regBytes = registers[i].toBytes();         
            res_message[index++] = regBytes[0];
            res_message[index++] = regBytes[1];           
        }

        return(res_message);      
      } catch (java.net.UnknownHostException e) {
        throw new ModbusCommunicationException(
            "Illegal slave ip address, current slave info["
            + slave_info.toString() +"]");
        
      } catch (net.wimpi.modbus.ModbusIOException e) {
       
        throw new ModbusCommunicationException(
            "Modbus Tcp Connection error during communication  ["+ e.toString() + "]");
        
      } catch (net.wimpi.modbus.ModbusSlaveException e) {
           
        throw new ModbusCommunicationException(
            "Modbus illeagl response from slave, the request was [" + req.getHexMessage() + "]" );
             
      } catch (ModbusException e) {
           
        throw new ModbusCommunicationException("Unknown Modbus exeception [" + e.toString() +"]");        
        
      } catch (NullPointerException e) {
        throw new ModbusCommunicationException(
              "No data received from slave, the request was [" + req.getHexMessage() +"]");
              
      } catch (IOException e) {      
       
        throw new ModbusCommunicationException(
         "network failue on setup TCPMasterConnection, please check slave information in configuration file, e.g. ip, port: current Slave info[" 
          + slave_info.toString() + "] detail error [" 
          + e.toString() +"]" );
        
      } catch(java.lang.ClassCastException e)  {         
        throw new ClassCastException(e.toString() + 
        ":response message is " + hexMessage  );    
      } finally{          
          /* new in version 2.0 */        
          con.close();
      }
           
    } // end of function 
    
    
    
    /**
     * Get items raw value from a one dimension array with given range. The array start from 
     * index = 1
     *
     * @since   1.0
     *
     * @version 1.0   use default MODBUS TCP conneciton is hasDefaultConnection() return true 
     *                otherwise create connection from slave_info,<B> but not set default connection </B>
     * 
     * @version 1.1   redefine the start index from 0 to 1     
     *
     * @param   slave_info  slave information such as ip, port.. 
     * @param   m_signal    contains necessary information for MODBUS request 
     * @param   start       requested items start index, the first item in array is 1 
     * @param   end         requested items end index , for example, start=1,end = 3 will request value 
     *                      array[1];array[2];array[3];
     *
     * @return  byte[]      <B>raw value</B> of requested signal in byte array (without modbus head bytes); <br>
     *                      <B>MSB first in return array if the requested signal has WORD type </B> <br>
     *                      for example, PLC_variable_W = 16#1234, return array is <br>
     *                      byte[0] = 16#12;byte[1] = 16#34;  <P>
     *    
     *                      <B>LSB first in return array if the requested siganl has DWORD type. </B>  <br>
     *                      for example, PLC_variable_DW = 16#12345678, return array is <br>
     *                      byte[0] = 16#56;byte[1] = 16#78;byte[2] = 16#12;byte[3] = 16#34;    <P> 
     *                      return signal value if given signal is not array variable( if getArraySize() = 1 ); <br>
     *                      null if no signal founded 
     *
     * @throws  ModbusCommunicationException when error occured during communication 
     *          such as network failure, illegal reqeust or requested index out of bound 
     *          start index;  <br>
     *
     *          FunctionIllegalInputException if function is called with illegal parameter     
     */
    public synchronized byte[] readArrayItemsRawValues(
      ModbusSlaveInfo slave_info, ModbusSignal m_signal,int start, int end)
      throws ModbusCommunicationException,FunctionIllegalInputException,Exception {
     

      // check input parameter 
      if ( (slave_info == null) 
        || (m_signal == null) 
        || (start < 1)
        || (start > end))  {
        String errStr = (m_signal==null)?"null" :Integer.toString(m_signal.getAddress());
        throw new FunctionIllegalInputException(
          "readArrayItems has illegal input on siganl[" + errStr 
          + "] with start index[" + start + "],end index [" + end + "]" );
      }
        
      // 1. create MODBUS TCP request
      ReadMultipleRegistersRequest req = new ReadMultipleRegistersRequest();
          
      
      try  {
        //1.1 calculate signal length 
        int ref        = m_signal.getAddress();
        int signal_len = 0;                       // length counte in WORD;         
        switch (m_signal.getMtype()) {
          case ModbusSignal.MTYPE_DW:             // double WORD signal 
            signal_len = 2;
            break;
          case ModbusSignal.MTYPE_W:              // WORD signal 
            signal_len = 1;
            break;
         default:  
        }     
        
        //1.2 calculate reference address 
        int ref_offset  = 0;                         // address off set             
        // calculate address off set value, for none array variable offset = 0      
        if ( m_signal.getArraysize() > 1 )  {
          ref_offset = (start -1) * signal_len;
        }      
        ref += ref_offset;                           // calculate reference address 
        
        //1.3 calculate total request length 
        int signal_num  = end - start +1;            // requested signal number
        int request_len = signal_num * signal_len;   // total length of WORD of request
        
        
        //1.4 check if need to do MODBUS TCP request for several times             
        boolean req_con = false;                     // flag of continue reqeust process after first request      
        int actual_ref  = ref;                       // actual reference
        int actual_len  = request_len;               // actual length 


        
        //1.5 set MODBUS TCP request fixed value       
        req.setTransactionID(slave_info.getTransactionID());
        req.setProtocolID(slave_info.getProtocolID());     
        req.setUnitID(slave_info.getUnitID());
        
        // create receive data byte array 
        byte [] rawValue = new byte[signal_num * signal_len * 2];           
        
        int index = 0;
        do {
          //1.6 set reference address and length of current request     
          if (request_len > MAX_WORD_IN_REQUEST ) {
            req_con    = true;          
            actual_len = MAX_WORD_IN_REQUEST;
          } else  {
            req_con    = false;                 
          }        
          
          req.setReference(actual_ref);
          req.setWordCount(actual_len);
        
//        System.out.println("Acutal Request : " + req.getHexMessage());
       
          // 2.  Open MODBUS TCP connection 
          // 2.1 Create connection if don't have default connection 
          if (! hasDefaultConnection() ) {
            InetAddress slave_add = InetAddress.getByName(slave_info.getSlaveIP());
            con = new TCPMasterConnection(slave_add);
            con.setPort(slave_info.getTcpPort());        
            con.setTimeout(slave_info.getSocketTimeout()); 
            
            // Create transaction 
            trans = new ModbusTCPTransaction();     
            trans.setRequest(req); 
             
            trans.setReconnecting(slave_info.getReconnect());       
            // set retry number when error occured 
            trans.setRetries(slave_info.getRetryNum());       
          }  


          if (!con.isConnected()) {
            con.connect();       // throw IOException if there is a network failure        
          }

          trans.setConnection(con);          
            
          //3. Send request 
          trans.setRequest(req);   
              
          trans.execute();  // throw Exception - ModbusSlaveException;ModbusIOException;ModbusException 
     
          //4. receive response 
          ReadMultipleRegistersResponse res = (ReadMultipleRegistersResponse) trans.getResponse();      
         
//         System.out.println("response is :" + res.getHexMessage());
       
          //5. return raw value 

          // new in version 2.0 
          Register[] registers = res.getRegisters();
                  
          /* convert register to byte array . each register is 2 byte array */
          
          for (int i = 0;  i <registers.length; i++ ){
              byte[] regBytes = registers[i].toBytes();         
              rawValue[index++] = regBytes[0];
              rawValue[index++] = regBytes[1];           
          }
         
          //6. check if need to continue request 
          if (req_con) {
            request_len -= actual_len;
            actual_ref  += actual_len;
            actual_len  = request_len;
            
//            System.out.println("request_len " + request_len);
//            System.out.println("actual_ref; " + actual_ref);
//            System.out.println("actual_len; " + actual_len);          
//            System.out.println("");
          }        

        } while (req_con);
        
        return(rawValue);
              
      } catch (java.net.UnknownHostException e) {
        throw new ModbusCommunicationException("Illegal information in slave address field [" 
        + slave_info.getSlaveIP() +"]");
        
      } catch (net.wimpi.modbus.ModbusIOException e) {
               
        throw new ModbusCommunicationException(
              "data cannot be read properly from the raw input stream of  ModbusTransport [" 
              +  slave_info.getSlaveIP() + "] detail error info["
              + e.toString() +"]");    
              
      } catch (net.wimpi.modbus.ModbusSlaveException e) {
           
        throw new ModbusCommunicationException(
              "Modbus illeagl response from slave, the request was [" 
              + req.getHexMessage() + "]" );
             
      } catch (ModbusException e) {
           
        throw new ModbusCommunicationException("Modbus exeception [" + e.toString() +"]");
        
      } catch (NullPointerException e) {
        throw new ModbusCommunicationException(
              "No data received from slave, the request was [" + req.getHexMessage() +"]");            
              
      } catch (IOException e) {
           
        throw new ModbusCommunicationException(  
          "network failue on setup TCPMasterConnection, please check slave information in configuration file, e.g. ip, port: current Slave info[" 
          + slave_info.toString() + "] detail information ["
          + e.toString() + "]" );                  
      } finally{          
          /* new in version 2.0 */        
          con.close();
      }
      
      
    } // end of function 
    
    
    /**
     * Write <B>a signal variable </B> value. The funcion will not check whether given signal 
     * is an array signal or not. The function is implemented by MODBUS function code 0x10  
     *  
     *
     * @since   1.0
     *
     * @version 1.0   use default MODBUS TCP conneciton is hasDefaultConnection() return true 
     *                otherwise create connection from slave_info,<B> but not set default connection </B>    
     *
     * @param   slave_info  slave information such as ip, port.. 
     * @param   m_signal    contains necessary information for MODBUS request. 
     * @param   values      raw value of given signal, for WORD signal use values[0] as signal value 
     *                      for DWORD signal use values[0] and values[1] as signal value <br>
     *
     * @param   swap        <B>only effective when m_signal is DWORD signal</B> Set to ture to use 
     *                      values[0] as MSW, values[1] as LSW. Otherwise values[0] is LSW , value[1] is MSW. <br>
     *                      for example by default values ={1,2} will set PLC DW varaible = 16#00020001; <br>
     *                      set swap = true will set PLC DW Variable = 16#00010002 
     *
     * @return  boolean     true if write is ok. 
     *
     * @throws  ModbusCommunicationException when error occured during communication 
     *          such as network failure, illegal reqeust or receive exception response from slave <br>
     *
     *          FunctionIllegalInputException if function is called with illegal parameter
     */ 
    public synchronized  boolean writeSignalValue(
      ModbusSlaveInfo slave_info, ModbusSignal m_signal,int[] values, boolean swap )
      throws ModbusCommunicationException,FunctionIllegalInputException,Exception{
      // check input parameter 
      if ( (slave_info == null) 
        || (m_signal == null) 
        || (values == null) )  {
        throw (new FunctionIllegalInputException("writeSignalValue has illegal input"));
      }
      
      if (  (m_signal.getMtype() == ModbusSignal.MTYPE_DW)
         && (values.length<2) ) {
        throw (new FunctionIllegalInputException("writeSignalValue has illegal input: values is too short for DWORD"));
      }
      
      WriteMultipleRegistersRequest req = new WriteMultipleRegistersRequest();    
      String hexMessage  = "";
      try  {
      
        //1.crate MODBUS request 
        int ref = m_signal.getAddress();
        req.setReference(ref);
        req.setTransactionID(slave_info.getTransactionID());
        req.setProtocolID(slave_info.getProtocolID());
        req.setUnitID(slave_info.getUnitID());      
        
        
        
        /* new in version 2.0 */
        //prepave value field
        Register[] reg_values = new Register[1]; 
          
        int dataLen = 0;
        
        // Very very important, must manually set the data length
        /* Data Length = 
         * Address      2   byte
         * Word CNT     2   byte   
         * Byte CNT     1   byte 
         * --------------------------    
         *            + 5 byte                       
         * Value0       2   byte
         * Value1       2   byte 
         * ...
         */      
        
        switch (m_signal.getMtype()) {
        case ModbusSignal.MTYPE_DW:             // double WORD signal              
            
          reg_values = new Register[2];  
          
          // swap value of values[0] and values[1]          
          if (swap)  {            
              reg_values[0] = new SimpleRegister(values[1]);
              reg_values[1] = new SimpleRegister(values[0]);
          } else  {
              reg_values[0] = new SimpleRegister(values[0]);
              reg_values[1] = new SimpleRegister(values[1]);
          }
          dataLen = 5 + 4;
          break;
        case ModbusSignal.MTYPE_W:              // WORD signal         
          reg_values[0] = new SimpleRegister(values[0]);
          dataLen = 5 + 2; 
          break;
        default:    
        }
        req.setDataLength(dataLen);
        req.setRegisters(reg_values);
        
        
        
        

//        System.out.println("Request is " + req.getHexMessage());
      
        // 2.  Open MODBUS TCP connection 
        // 2.1 Create connection if don't have default connection 
        if (! hasDefaultConnection() ) {
          InetAddress slave_add = InetAddress.getByName(slave_info.getSlaveIP());
          con = new TCPMasterConnection(slave_add);
          con.setPort(slave_info.getTcpPort());       
          con.setTimeout(slave_info.getSocketTimeout()); 
          // Create transaction 
          trans = new ModbusTCPTransaction();     
          trans.setRequest(req); 
          trans.setReconnecting(slave_info.getReconnect());       
          // set retry number when error occured 
          trans.setRetries(slave_info.getRetryNum());
        }  
        

        if (!con.isConnected()) {
          con.connect();       // Throw IOException if there is a network failure  
        }

        if (Modbus.debug){
            System.out.println(Thread.currentThread().getName() + "_" + Thread.currentThread().getId() +
                " writeSignalValue will send the request :" + req.getHexMessage()) ;
   
        }
        
        trans.setConnection(con);          
          
        //3. Send request 
        trans.setRequest(req);   
             
        trans.execute();  // throw Exception - ModbusSlaveException;ModbusIOException;ModbusException 

        WriteMultipleRegistersResponse res = (WriteMultipleRegistersResponse) trans.getResponse();
        if (Modbus.debug){
            if (res == null) {
                System.out.println(Thread.currentThread().getName() + "_" + Thread.currentThread().getId() +
                        " writeSignalValue get the response = null") ;
            } else {
                hexMessage = res.getHexMessage();
                System.out.println(Thread.currentThread().getName() + "_" + Thread.currentThread().getId() +
                        " writeSignalValue get the response " + res.getHexMessage()) ;                
            } 
        }
        
        
        //4. receive response , check if it is MODBUS TCP exception response       
        //WriteMultipleRegistersResponse res = (WriteMultipleRegistersResponse) trans.getResponse();            

       
        return (true);
      } catch (java.net.UnknownHostException e) {
        throw new ModbusCommunicationException("Illegal information in slave address field [" 
        + slave_info.getSlaveIP() +"]");
        
      } catch (net.wimpi.modbus.ModbusIOException e) {
            
        throw new ModbusCommunicationException(
              "data cannot be written properly from the raw input stream of  ModbusTransport [" 
              +  slave_info.getSlaveIP() + "] detail error info["
              +  e.toString() +"]");    
              
      } catch (net.wimpi.modbus.ModbusSlaveException e) {
            
        throw new ModbusCommunicationException(
              "Modbus illeagl response from slave, the request was [" 
              + req.getHexMessage() + "]" );
             
      } catch (ModbusException e) {
            
        throw new ModbusCommunicationException("Unknown Modbus exeception [" + e.toString() +"]");
        
      } catch (NullPointerException e) {
        throw new ModbusCommunicationException(
              "No data received from slave, the request was [" + req.getHexMessage() +"]");
              
      } catch (IOException e) {
        
        throw new ModbusCommunicationException(
          "network failue on setup TCPMasterConnection, please check slave information in configuration file, e.g. ip, port: current Slave info[" 
          + slave_info.toString() + "] detail information ["
          + e.toString() + "]" );      
          
      } catch(java.lang.ClassCastException e)  {         
        throw new ClassCastException(e.toString() + 
        ":response message is " + hexMessage  );    
      } finally{          
          /* new in version 2.0 */        
          con.close();
      }
      
    } // end of function 
    
    /**
     * 
     * @param dataLen    size of array
     * @param maxWrite   the maximal size of block data
     * @param loop       the number of actual block data in a array with the size dataLen
     * @return
     */
    private int getLength(int dataLen, int maxWrite,int loop){
        //actual size of available data in the block
        int actLen = (dataLen ==maxWrite*loop) ? maxWrite : (dataLen > maxWrite*loop) ? maxWrite : dataLen%maxWrite;      
        
        return actLen;
    }

    /**
     * The variable "MAX_WRITE_WORD" defines the maximum frame length 
     * Determine the start- and end index for each data block. Calling of method "writeArrayValueBlock"
     * 
     * @param   slave_info  slave information such as ip, port.. 
     * @param   m_signal    contains necessary information for MODBUS request. 
     * @param   values      raw value of given signal, for WORD signal use values[0] as signal value
     *                      for DWORD signal use values[0] and values[1] as signal value 
     * @param   start       start index in array variable ,the first item is 1
     * @param   end         end index in array variable 
     * @param   swap        <B>only effective when m_signal is DWORD signal</B> Set to ture to use
     *                      values[0] as MSW, values[1] as LSW. Otherwise values[0] is LSW , value[1] is MSW. <br>
     *                      for example by default values ={1,2} will set PLC DW varaible = 16#00020001;  <br>
     *                      set swap = true will set PLC DW Variable = 16#00010002
     *
     * @return  boolean     true if write is ok; false if m_signal is not a array siganl 
     *
     * @throws  ModbusCommunicationException when error occured during communication 
     *          such as network failure, illegal reqeust or receive exception response from slave  <br>
     *
     *          FunctionIllegalInputException if function is called with illegal parameter
     */
    public synchronized  boolean writeArrayValue( 
            ModbusSlaveInfo slave_info, ModbusSignal m_signal,int[] values, int start, int end , boolean swap ) 
            throws ModbusCommunicationException,FunctionIllegalInputException,Exception {

        
            // check input parameter 
            if ( (slave_info == null) 
              || (m_signal == null) 
              || (values == null)
              || (start > end )
              || (start < 1 )
              || (end > values.length))  {
              throw (new FunctionIllegalInputException("writeArrayValue has illegal input" 
                    + "SlaveInfo [" + slave_info.toString() +"] " 
                    + "siganl [" + m_signal.toString() +"] "
                    + "start index [" + Integer.toString(start) +"] "
                    + "end index [" + Integer.toString(end) + "]"
                    + "swap [" + (swap?"YES":"NO") + "]" 
                    ));
            }
            
            
            int dataLen = values.length;
            //determin the number of data block dependt of MAX_WRITE_WORD
            
            
            int loopMax = 
                ((dataLen % MAX_WRITE_WORD) == 0) ? 
                        dataLen / MAX_WRITE_WORD : 
                        (dataLen/MAX_WRITE_WORD)+1 ;
            int loopCnt = 1;          
            int startIndex = 1;
            int endIndex = 0;
            boolean ok = false;
            int blockSize = 0;
            int cnt=0;
            
            while(loopCnt<=loopMax){
                
                blockSize = getLength(dataLen, MAX_WRITE_WORD,loopCnt);
                endIndex += blockSize;              
                int[] rawValues = new int[blockSize];
                          
                for(int arrayCnt=0; arrayCnt<blockSize;arrayCnt++,cnt++){
                    rawValues[arrayCnt] = values[cnt];
                }
                
                //System.out.println("Start = "+ startIndex + ", end = " + endIndex + ", address = " + m_signal.getAddress());
                
                ok = writeArrayValueBlock(slave_info,m_signal,rawValues,startIndex,endIndex,swap);
                //determin the new startIndex
                startIndex = endIndex+1;
                loopCnt++;
                
            }//end of while loop
            
            return ok;

     }// end of function 
    
    
    /**
     * Write value to <B> 1 dimuarray array variable </B> with given range. 
     * Varialbe MAX_WRITE_WORD defines maximum WORD value that can be handled by this funciton. 
     * The function will throw FunctionIllegalInputException if given range over MAX_WRITE_WORD. 
     * The function is implemented by MODBUS function code 0x10  
     *  
     *
     * @since   1.0
     *
     * @version 1.0   use default MODBUS TCP conneciton is hasDefaultConnection() return true 
     *                otherwise create connection from slave_info,<B> but not set default connection </B>
     * 
     * @version 1.1   redefine array index start from 1    
     *
     * @param   slave_info  slave information such as ip, port.. 
     * @param   m_signal    contains necessary information for MODBUS request. 
     * @param   values      raw value of given signal, for WORD signal use values[0] as signal value
     *                      for DWORD signal use values[0] and values[1] as signal value 
     * @param   start       start index in array variable ,the first item is 1
     * @param   end         end index in array variable 
     * @param   swap        <B>only effective when m_signal is DWORD signal</B> Set to ture to use
     *                      values[0] as MSW, values[1] as LSW. Otherwise values[0] is LSW , value[1] is MSW. <br>
     *                      for example by default values ={1,2} will set PLC DW varaible = 16#00020001;  <br>
     *                      set swap = true will set PLC DW Variable = 16#00010002
     *
     * @return  boolean     true if write is ok; false if m_signal is not a array siganl 
     *
     * @throws  ModbusCommunicationException when error occured during communication 
     *          such as network failure, illegal reqeust or receive exception response from slave  <br>
     *
     *          FunctionIllegalInputException if function is called with illegal parameter
     */   
    private  boolean writeArrayValueBlock( 
      ModbusSlaveInfo slave_info, ModbusSignal m_signal,int[] values, int start, int end , boolean swap ) 
      throws ModbusCommunicationException,FunctionIllegalInputException,Exception{
      // check input parameter 
      if ( (slave_info == null) 
        || (m_signal == null) 
        || (values == null)
        || (start > end )
        || (start < 0 )){
        throw (new FunctionIllegalInputException("writeArrayValue has illegal input " 
              + "SlaveInfo [" + slave_info.toString() +"] " 
              + "siganl [" + m_signal.toString() +"] "
              + "start index [" + Integer.toString(start) +"] "
              + "end index [" + Integer.toString(end) + "]"
              + "swap [" + (swap?"YES":"NO") + "]" 
              ));
      }
      
      // check if values arry is enough 
      int signal_num  = end - start+1;    
      int signal_len  = 1;
      int request_len = 1;
      
      switch (m_signal.getMtype()) {
          case ModbusSignal.MTYPE_DW:
            signal_len = 2;
            //mah
            request_len = signal_num;      
            if (values.length < request_len) {
              throw (new FunctionIllegalInputException(
                "writeArrayValue has illegal input: values is too short for requested DWORD array[ "
                + start + " - " + end + " ]"  + "["  +  m_signal.getAddress() +"]"));
            }
          break;
          case ModbusSignal.MTYPE_W:
            signal_len =1;
            //mah
            request_len = signal_num;
                  
            if (values.length < request_len) {
              throw (new FunctionIllegalInputException(
                "writeArrayValue has illegal input: values is too short for requested WORD array[ "
                + start + " - " + end + " ]"  + "["  +  m_signal.getAddress() +"]"));
            }      
          default:
      }
      
     
      if (request_len>MAX_WRITE_WORD) {
        throw( new FunctionIllegalInputException(
          "writeArrayValue request siganl over limit.Maximum WORD,for this function is " +MAX_WRITE_WORD + "["  +  m_signal.getAddress() +"]") );
      }
      
      // check if support input rang 
      if (m_signal.getArraysize() < 2) return(false); 
   
      WriteMultipleRegistersRequest req = new WriteMultipleRegistersRequest();
      try {
        // 1   create MODBUS TCP request 
        // 1.1 calculate reference address 
        int ref = m_signal.getAddress();         
        int ref_offset = (start - 1)/** signal_len*/;      
        ref += ref_offset; 
        
        req.setReference(ref);
        req.setTransactionID(slave_info.getTransactionID());
        req.setProtocolID(slave_info.getProtocolID());
        req.setUnitID(slave_info.getUnitID());
        
        // 1.2 creat raw value array
        
        Register[] reg_values = new Register[1];      
        int dataLen = 0;      
        /* Data Length = 
         * Address      2   byte
         * Word CNT     2   byte   
         * Byte CNT     1   byte 
         * --------------------------    
         *            + 5 byte                       
         * Value0       2   byte
         * Value1       2   byte 
         * ...
         */         
        
        switch (m_signal.getMtype()) {
        case ModbusSignal.MTYPE_DW:        
          
          reg_values = new Register[request_len];
         
          if (swap) {          
            // swap value of v[i] and v[i+1]
            for (int i=0;i<request_len;i+=2) {
              reg_values[i] = new SimpleRegister(values[i+1]);
              reg_values[i+1] = new SimpleRegister(values[i]);                         
            }
          } else  {
            for (int i =0;i<request_len;i++) {
              reg_values[i] = new SimpleRegister(values[i]);              
          
            }          
          }
          dataLen = 5 + request_len * 2;
        break;
        case ModbusSignal.MTYPE_W:                         
          reg_values = new Register[request_len];
          
          for (int i =0;i<request_len;i++) {
            reg_values[i] = new SimpleRegister(values[i]);                      
          }
          dataLen = 5 + request_len * 2;
        break;
        default:
        } 
        req.setDataLength(dataLen);
        req.setRegisters(reg_values);
        

        // 2.  Open MODBUS TCP connection 
        // 2.1 Create connection if don't have default connection 
        if (! hasDefaultConnection() ) {
          InetAddress slave_add = InetAddress.getByName(slave_info.getSlaveIP());
          con = new TCPMasterConnection(slave_add);
          con.setPort(slave_info.getTcpPort());    
          con.setTimeout(slave_info.getSocketTimeout()); 
          
          // Create transaction 
          trans = new ModbusTCPTransaction();     
          trans.setRequest(req); 
          // close  transaction after each execute       
          trans.setReconnecting(slave_info.getReconnect());       
          // set retry number when error occured 
          trans.setRetries(slave_info.getRetryNum());
        }  
        

        if (!con.isConnected()) {
          con.connect();       // throw IOException if there is a network failure 
        }

        trans.setConnection(con);          
        if (Modbus.debug){
            System.out.println(Thread.currentThread().getName() + "_" + Thread.currentThread().getId() +
                    "_[" + this.hashCode() + "] writeArrayValueBlock start to send request " + req.getHexMessage()) ;            
        }
        
        //3. Send request 
        trans.setRequest(req);   
             
        trans.execute();  // throw Exception - ModbusSlaveException;ModbusIOException;ModbusException 
        
        //4. receive response , check if it is MODBUS TCP exception response 
        WriteMultipleRegistersResponse res = (WriteMultipleRegistersResponse) trans.getResponse();
        if (Modbus.debug){
            if (res == null) {
                System.out.println(Thread.currentThread().getName() + "_" + Thread.currentThread().getId() +
                        " writeArrayValueBlock get the response = null") ;
            } else {
                System.out.println(Thread.currentThread().getName() + "_" + Thread.currentThread().getId() +
                        " writeArrayValueBlock get the response " + res.getHexMessage()) ;                
            } 
        }
        return (true);      
     
      } catch (java.net.UnknownHostException e) {
        throw new ModbusCommunicationException("Illegal information in slave address field [" 
        + slave_info.getSlaveIP() +"]");
        
      } catch (net.wimpi.modbus.ModbusIOException e) {
            
        throw new ModbusCommunicationException(
              "data cannot be read properly from the raw input stream of  ModbusTransport [" 
              +  slave_info.getSlaveIP() + "] detail error info["
              +  e.toString() + "]");    
              
      } catch (net.wimpi.modbus.ModbusSlaveException e) {
            
        throw new ModbusCommunicationException(
              "Modbus illeagl response from slave, the request was [" 
              + req.getHexMessage() + "]" );
             
      } catch (ModbusException e) {
            
        throw new ModbusCommunicationException("Unknown Modbus exeception [" + e.toString() +"]");
        
      } catch (NullPointerException e) {
        throw new ModbusCommunicationException(
              "No data received from slave, the request was [" + req.getHexMessage() +"]");
              
      } catch (IOException e) {
        
        throw new ModbusCommunicationException(
          "network failue on setup TCPMasterConnection, please check slave information in configuration file, e.g. ip, port: current Slave info[" 
          + slave_info.toString() + "] detail information ["
          + e.toString() + "]" );
      } finally {
          con.close();
      }   
      
    }// end of function 
    
    
    
    /**
     * Read items raw value from a two dimensions array. The function provides possible to 
     * fetch data from row or column with index definition. <br> The requeted array should 
     * start with item[1][1].
     *      
     * for example for PLC array Counter_annual[1..5][1..21] 
     * Set parametr start = 1;end = 5;index1d =-1 index2d = 2 will return raw value of 
     * Counter_annual[1..5][2]. (one cloumn) <br>
     * Set parametr start = 1;end = 5;index1d =2 index2d = -1 will return raw value of 
     * Counter_annual[2][1..5]. (one row) <br>
     * 
     * Communication handshaking process: <br>
     * The function first send index to slave. After process ok then read the value from slave. <br>
     * Steps: <br>
     * [1] write trigger bit at m_signal.getWritePointAddress(). The trigger is defined as 
     * bit14 = 1 -> request for one column value, bit15 = 1 -> request for one row value <br> 
     * [2] read arry status (at m_siganl.getReadyAddress())to check if requested items are ready to read. <br>
     * [3] read items in given range if array status = 1. otherwise continue read status till timeout 
     *     Timeout is defined by variable MAX_RETRY_NUM and RETRY_INTERVAL_MS <br>
     * [4] reset array status siganl (to 0) <br>
     * <p> function contains Thread.sleep method when waiting array copy status    
     *
     *
     * @version 1.0   use default MODBUS TCP conneciton is hasDefaultConnection() return true 
     *                otherwise create connection from slave_info,<B> but not set default connection </B>    
     * @version 1.1   Insert delay time after read array copy ready siganl to guarantee get the complete 
     *                value from PLC: 
     * @version 2.0   Change to fetch data from row and column      
     *
     * @param   slave_info  slave information such as ip, port.. 
     * @param   m_signal    contains necessary information for MODBUS request 
     * @param   start       requested first item index in the dimension, first item is 1
     * @param   end         requested last item index in the dimension, first item is 1
     * @param   index1d     requested index of first dimension, the first item is 1, -1 will ignore the
     *                      request of first dimension   
     * @param   index2d     requested index of second dimension, the first item is 1, -1 will ignore the 
     *                      request of second dimension 
     *
     * @return  byte[]      <B>raw value</B> of requested signal in byte array (without modbus head bytes); <br>
     *                      <B>MSB first in return array if the requested signal has WORD type </B> <br>
     *                      for example, PLC_variable_W = 16#1234, return array is 
     *                      byte[0] = 16#12;byte[1] = 16#34;  <P>
     *    
     *                      <B>LSB first in return array if the requested siganl has DWORD type. </B>  <br>
     *                      for example, PLC_variable_DW = 16#12345678, return array is <br>
     *                      byte[0] = 16#56;byte[1] = 16#78;byte[2] = 16#12;byte[3] = 16#34;    <P>
     *                      
     *
     * @throws  ModbusCommunicationException when error occured during communication 
     *          such as network failure, illegal reqeust or requested index out of bound 
     *          start index; <br>
     *
     *          FunctionIllegalInputException if function is called with illegal parameter such as <br>
     *          - given signal getArraySize2D() equal 0 <br> 
     *          - index1d = -1 and index2d = -1 <br>
     *           
     */  
    public synchronized byte[] read2DArrayItemsRawValues(
      ModbusSlaveInfo slave_info, ModbusSignal m_signal,int start, int end,int index1d,int index2d) 
      throws ModbusCommunicationException,FunctionIllegalInputException,Exception{
      
      // check input parameter 
      if ( (slave_info == null) 
        || (m_signal == null) 
        || (start < 1)
        || (start > end)
        || (m_signal.getArraysize2d() == 0)
        || (index1d > m_signal.getArraysize2d())
        || (index2d > m_signal.getArraysize()) ) 
        throw new FunctionIllegalInputException(
          "read2DArrayItems has illegal input; signal[" 
         + ((m_signal!=null)? Integer.toString(m_signal.getAddress()):"null")
         +"] start index[" + Integer.toString(start) + "] end index[" + Integer.toString(end) 
         +"] index1d [" + Integer.toString(index1d) +"]"
         +"] index2d [" + Integer.toString(index2d) +"]");
      
      
       
      //1.  set siganl write point value 
      //1.1 create temp Modbus Signal with write point address 
      ModbusSignal writePoint = new ModbusSignal(
                                        m_signal.getWritepointAddress(),
                                        ModbusSignal.MTYPE_W,
                                        1);
      try  {

        
        //1.2 create trigger value 
        if ((index1d * index2d) > 0){
            throw new FunctionIllegalInputException(
                    "read2DArrayItems has illegal input; signal[" 
                   + ((m_signal!=null)? Integer.toString(m_signal.getAddress()):"null")
                   +"] start index[" + Integer.toString(start) + "] end index[" + Integer.toString(end) 
                   +"] index1d [" + Integer.toString(index1d)
                   +"] index2d [" + Integer.toString(index2d) +"]");          
        }
        
        //1.3 set trigger bit and write point value  
        int trigger = 0x0000;
        int[] value = new int[1];
        if (index1d == -1 ){
            // ignore the first dimension request (row), request for one cloumn value  
            trigger = 0x4000;
            value[0] = index2d;
        }
        if (index2d == -1 ){
            // ignore the second dimension request (column), request for one row value
            trigger = 0x8000;
            value[0] = index1d;
        }      
                    
        value[0] = value[0] | trigger;
        
//        System.out.println("WP with trigger is " + Integer.toHexString(value[0]));
//        System.out.println("WritePoint is " + writePoint.toString() );
        
        //1.5 set write point vlaue together with trigger bit 
        writeSignalValue(slave_info,writePoint,value,false);
        
        //2   read array status bit 
        ModbusSignal arrayStatus = new ModbusSignal(
                                        m_signal.getReadyAddress(),
                                        ModbusSignal.MTYPE_W,
                                        1);      
        int waitNum = 0;
        boolean arrayReady = false;
        // 2.1 check arrayOk signal till arrayOK[1] = 1 or waitNum > MAX_RETRY_NUM
        do {
          byte[] status = readSignalRawValue(slave_info,arrayStatus);
          
//          System.out.println("waiting for PLC prepare array " + waitNum );
          
          waitNum ++;        
          
          if (status[1] == 1) {
            arrayReady = true;
            waitNum = MAX_RETRY_NUM + 1; 
          } else  {
            java.lang.Thread.sleep(slave_info.getRetry_interval());        
          }

        } while (waitNum<MAX_RETRY_NUM);
        
        // 2.2 throw exception when time out 
        if (!arrayReady) {
          throw new ModbusCommunicationException (
            "Waiting for array ready timeout when requst [" + m_signal.getAddress() 
            +"] with index [" + index2d + "]"); 
        }            
       
       
        // new changes in Rev1.1
        java.lang.Thread.sleep(slave_info.getRetry_interval());
        
        //3.  Read array data
        //3.1 prepare new siganl for reading array
        ModbusSignal tempSignal = (ModbusSignal)m_signal.clone();
        
        if (index2d == -1){
            tempSignal.setArraysize(m_signal.getArraysize2d());    
        }
        
        byte[] raw_values = readArrayItemsRawValues(slave_info,tempSignal,start,end);      

        //4.  Reset array status 
        value[0] = 0;
        writeSignalValue(slave_info,arrayStatus,value,false);
        
        return (raw_values);
      } catch (ModbusCommunicationException e) {
        throw new ModbusCommunicationException ("read2DArrayItems - " + e.getMessage());
      } catch (InterruptedException e) {
        throw new ModbusCommunicationException (
        "read2DArrayItems - Waiting for array status was interrupted by ohter thread " );
      } catch (CloneNotSupportedException e){
          throw new ModbusCommunicationException (
          "read2DArrayItems - ModbusSiganl can not be copied " );
      }   
    }
    
    public synchronized boolean write2DArrayItemsRawValues(
            ModbusSlaveInfo slave_info, ModbusSignal m_signal,int start, int end,int index1d,int index2d) 
            throws ModbusCommunicationException,FunctionIllegalInputException,Exception{
            
            // check input parameter 
            if ( (slave_info == null) 
              || (m_signal == null) 
              || (start < 1)
              || (start > end)
              || (m_signal.getArraysize2d() == 0)
              || (index1d > m_signal.getArraysize())
              || (index2d > m_signal.getArraysize2d()) ) 
              throw new FunctionIllegalInputException(
                "read2DArrayItems has illegal input; signal[" 
               + ((m_signal!=null)? Integer.toString(m_signal.getAddress()):"null")
               +"] start index[" + Integer.toString(start) + "] end index[" + Integer.toString(end) 
               +"] index1d [" + Integer.toString(index1d) +"]"
               +"] index2d [" + Integer.toString(index2d) +"]");
            
            
            //System.arraycopy(Matrix[2], 2, copyTo, 0, 5); 
            
            //1.  set siganl write point value 
            //1.1 create temp Modbus Signal with write point address 
            ModbusSignal writePoint = new ModbusSignal(
                                              m_signal.getWritepointAddress(),
                                              ModbusSignal.MTYPE_W,
                                              1);
            try  {

              /*
              //1.2 create trigger value 
              if ((index1d * index2d) > 0){
                  throw new FunctionIllegalInputException(
                          "read2DArrayItems has illegal input; signal[" 
                         + ((m_signal!=null)? Integer.toString(m_signal.getAddress()):"null")
                         +"] start index[" + Integer.toString(start) + "] end index[" + Integer.toString(end) 
                         +"] index1d [" + Integer.toString(index1d)
                         +"] index2d [" + Integer.toString(index2d) +"]");          
              }
              */
              //1.3 set trigger bit and write point value  
              int trigger = 0x0000;
              int[] value = new int[1];
              if (index1d == -1 ){
                  // ignore the first dimension request (row), request for one cloumn value  
                  trigger = 0x4000;
                  value[0] = index2d;
              }
              if (index2d == -1 ){
                  // ignore the second dimension request (column), request for one row value
                  trigger = 0x8000;
                  value[0] = index1d;
              }      
                          
              value[0] = value[0] | trigger;
              
//              System.out.println("WP with trigger is " + Integer.toHexString(value[0]));
//              System.out.println("WritePoint is " + writePoint.toString() );
              
              //1.5 set write point vlaue together with trigger bit 
              writeSignalValue(slave_info,writePoint,value,false);
              
              //2   read array status bit 
              ModbusSignal arrayStatus = new ModbusSignal(
                                              m_signal.getReadyAddress(),
                                              ModbusSignal.MTYPE_W,
                                              1);      
              int waitNum = 0;
              boolean arrayReady = false;
              // 2.1 check arrayOk signal till arrayOK[1] = 1 or waitNum > MAX_RETRY_NUM
              do {
                byte[] status = readSignalRawValue(slave_info,arrayStatus);
                
//                System.out.println("waiting for PLC prepare array " + waitNum );
                
                waitNum ++;        
                
                if (status[1] == 1) {
                  arrayReady = true;
                  waitNum = MAX_RETRY_NUM + 1; 
                } else  {
                  java.lang.Thread.sleep(slave_info.getRetry_interval());        
                }

              } while (waitNum<MAX_RETRY_NUM);
              
              // 2.2 throw exception when time out 
              if (!arrayReady) {
                throw new ModbusCommunicationException (
                  "Waiting for array ready timeout when requst [" + m_signal.getAddress() 
                  +"] with index [" + index2d + "]"); 
              }            
             
             
              // new changes in Rev1.1
              java.lang.Thread.sleep(slave_info.getRetry_interval());
              
              //3.  Read array data
              //3.1 prepare new siganl for reading array
              ModbusSignal tempSignal = (ModbusSignal)m_signal.clone();
              
              if (index2d == -1){
                  tempSignal.setArraysize(m_signal.getArraysize2d());    
              }
              
              byte[] raw_values = readArrayItemsRawValues(slave_info,tempSignal,start,end);      

              //4.  Reset array status 
              value[0] = 0;
              writeSignalValue(slave_info,arrayStatus,value,false);
              
              return true;
            } catch (ModbusCommunicationException e) {
              throw new ModbusCommunicationException ("read2DArrayItems - " + e.getMessage());
            } catch (InterruptedException e) {
              throw new ModbusCommunicationException (
              "read2DArrayItems - Waiting for array status was interrupted by ohter thread " );
            } catch (CloneNotSupportedException e){
                throw new ModbusCommunicationException (
                "read2DArrayItems - ModbusSiganl can not be copied " );
            }   
          }
    /**
     * Disconnect current connection to MODBUS slave 
     * 
     */
    public synchronized void disConnect() {
      if ( (con != null) && con.isConnected() ) {
        con.close();
      }
    }
      

    
  }










